package org.tulkas.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.entity.DeflateDecompressingEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.cache.CachingHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tulkas.http.headers.MediaTypes;
import org.tulkas.http.headers.RequestHeaders;
import org.tulkas.http.headers.ResponseHeaders;
import org.tulkas.util.HttpUtil;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public abstract class BaseClient {
	private static Logger logger = LoggerFactory.getLogger(BaseClient.class);
	private static final String CONNECTION_CONTEXT_ATTR = "tulkas-request_headers";

	private String clientName = null;

	public BaseClient(String clientName) {
		this.clientName = clientName;
	}

	public synchronized String getClientName() {
		return clientName;
	}

	public ClientOptions getClientOptions() {
		return ClientManager.getInstance().getClientOptions(clientName);
	}

	/**
	 * Set request headers for opening web page
	 * 
	 * @param reqHeaders
	 * @param url
	 * @throws MalformedURLException
	 */
	protected void prepareBasicRequestHeaders(BaseConn httpBase, String fromUrl)
			throws MalformedURLException {
		ClientOptions options = getClientOptions();
		RequestHeaders reqHds = httpBase.getRequestHeaders();
		if (reqHds.getAccept() == null)
			reqHds.setAccept("*/*");
		if (reqHds.getAcceptEncoding() == null)
			reqHds.setAcceptEncoding(RequestHeaders.CONTENT_ENCODINGs.GZIP
					+ "," + RequestHeaders.CONTENT_ENCODINGs.DEFLATE);
		if (reqHds.getAcceptLanguage() == null)
			reqHds.setAcceptLanguage(options.getDefaultLanguage() + ",*");

		if (reqHds.getAcceptCharset() == null) {
			String charset = null;
			if (reqHds.getContentType() != null) {
				charset = HttpUtil.parseCharsetFromContentType(reqHds
						.getContentType());
			}
			if (charset == null)
				charset = options.getDefaultCharset();
			reqHds.setAcceptCharset(charset + ",*");
		}

		if (reqHds.getContentType() != null) {
			String ct = reqHds.getContentType();
			String mt = HttpUtil.parseMediaTypeFromContentType(ct);
			String oriCs = HttpUtil.parseCharsetFromContentType(ct);
			if (mt != null && oriCs == null) {
				ct = mt + "; charset=" + options.getDefaultCharset();
				reqHds.setContentType(ct);
			} else if (mt == null && oriCs != null) {
				ct = MediaTypes.TYPICAL.ARBITRARY_BINARY + ct;
				reqHds.setContentType(ct);
			}
		}

		if (reqHds.getReferer() == null && fromUrl != null)
			reqHds.setReferer(fromUrl);
	}

	protected void handleResponseStatus(HttpResponse response, BaseConn conn) {
		conn.setHttpStatusCode(response.getStatusLine().getStatusCode());
		conn.setHttpReasonPhase(response.getStatusLine().getReasonPhrase());
		conn.getResponseHeaders().setHeaders(response.getAllHeaders());
	}

	protected void handleResponseData(HttpResponse response, BaseConn conn,
			OutputStream stream) throws IOException {
		HttpEntity entity = response.getEntity();
		String encoding = conn.getResponseHeaders().getContentEncoding();
		if (ResponseHeaders.CONTENT_ENCODINGs.GZIP.equalsIgnoreCase(encoding)) {
			entity = new GzipDecompressingEntity(entity);
		} else if (ResponseHeaders.CONTENT_ENCODINGs.DEFLATE
				.equalsIgnoreCase(encoding)) {
			entity = new DeflateDecompressingEntity(entity);
		}

		int bufferSize = response.getParams().getIntParameter(
				CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8192);
		if (entity != null) {
			InputStream in = entity.getContent();
			try {
				byte[] buffer = new byte[bufferSize];
				int read;
				long length = 0;
				while (true) {
					read = in.read(buffer);
					if (read <= 0)
						break;
					stream.write(buffer, 0, read);
					length += read;
					conn.setReceivedLength(length);
					if (conn.isInterrupted()) {
						break;
					}
				}
			} finally {
				in.close();
			}
		}
	}

	protected HttpResponse httpExecute(HttpRequestBase request, BaseConn conn)
			throws IOException {
		HttpContext context = new BasicHttpContext();
		request.setHeaders(conn.getRequestHeaders().getAllHeaders());
		context.setAttribute(CONNECTION_CONTEXT_ATTR, conn);
		HttpResponse response = ClientManager.getInstance().getHttpClient(
				clientName).execute(request, context);
		CacheResponseStatus cacheStatus = (CacheResponseStatus) context
				.getAttribute(CachingHttpClient.CACHE_RESPONSE_STATUS);

		if (cacheStatus != null
				&& (CacheResponseStatus.CACHE_HIT.equals(cacheStatus)
						|| CacheResponseStatus.VALIDATED.equals(cacheStatus) || CacheResponseStatus.CACHE_MODULE_RESPONSE
						.equals(cacheStatus)))
			conn.setCached(true);
		return response;
	}

	protected void logAfterResponse(BaseConn httpBase, String logStart) {
		String msg = logStart;
		if (httpBase.isError()) {
			String text = msg + " failed! Status: "
					+ httpBase.getHttpStatusCode() + " "
					+ httpBase.getHttpReasonPhase();
			if (httpBase.getException() != null)
				logger.error(text, httpBase.getException());
			else
				logger.error(text);
		} else {
			logger.info(msg + ". Status: " + httpBase.getHttpStatusCode() + " "
					+ httpBase.getHttpReasonPhase()
					+ (httpBase.isInterrupted() ? ". Interrupted." : "."));
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Request Headers: " + httpBase.getRequestHeaders());
			logger.trace("Response Headers: " + httpBase.getResponseHeaders());
		}
	}
}
